#!/bin/bash
# vim:ts=4:sw=4:noexpandtab
#
# Copyright (c) 2004-2014 Parallels IP Holdings GmbH.
# All rights reserved.
# http://www.parallels.com


export PATH=/bin:/sbin:/usr/bin:/usr/sbin

BUNDLE_PATH="$(cd "`dirname "${0}"`/../../" && pwd)"
RC=0

RC_OTHER_PD_DETECTED=1
RC_OK=0

PD3_UNINSTALLER="${BUNDLE_PATH}/Contents/Resources/pd30uninstaller.h"
if [ -e "/Library/Parallels/Parallels Service.app/Contents/Resources/Uninstaller.sh" ]; then
	UNINSTALLER_SCRIPT="/Library/Parallels/Parallels Service.app/Contents/Resources/Uninstaller.sh"
elif [ -e "/Library/Parallels/Parallels Server.app/Contents/Resources/Uninstaller.sh" ]; then
	UNINSTALLER_SCRIPT="/Library/Parallels/Parallels Server.app/Contents/Resources/Uninstaller.sh"
fi


log() {
	logger -p install.info -t 'Parallels Uninstaller' "$@"
}

snow_leopard_receipts_switcheroo() {
	# Copy receipts to place where Uninstaller expect 'em
	log "Snow Leopard receipts switcheroo section"
	if [ -d "/var/db/receipts" ]; then
		log "Will copy receipts"
		receipts_old_dir="/Library/Parallels/Parallels Service.app/Contents/Receipts"
		receipts_dir="/var/db/receipts"
		pd4_receipt_old="${receipts_old_dir}/Parallels Desktop 4.pkg/Contents/Archive.bom"
		pd4_receipt="${receipts_dir}/com.parallels.desktop.bom"
		pe4_receipt_old="${receipts_old_dir}/Parallels Explorer 4.pkg/Contents/Archive.bom"
		pe4_receipt="${receipts_dir}/com.parallels.desktop.explorer.bom"
		pit4_receipt_old="${receipts_old_dir}/Parallels Image Tool 4.pkg/Contents/Archive.bom"
		pit4_receipt="${receipts_dir}/com.parallels.server.imagetool.bom"
		pt4_receipt_old="${receipts_old_dir}/Parallels Transporter 4.pkg/Contents/Archive.bom"
		pt4_receipt="${receipts_dir}/com.parallels.server.transporter.bom"
		pdr106_receipt_old="${receipts_old_dir}/Parallels Drivers 10.6.pkg/Contents/Archive.bom"
		pdr106_receipt="${receipts_dir}/com.parallels.desktop.drivers.106"

		# Desktop package
		if [ -e "${pd4_receipt_old}" -a ! -e "${pd4_receipt}" ]; then
			cp -f "${pd4_receipt_old}" "${pd4_receipt}"
			log "Receipt desktop copied with status $?"
		fi

		# Explorer package
		if [ -e "${pe4_receipt_old}" -a ! -e "${pe4_receipt}" ]; then
			cp -f "${pe4_receipt_old}" "${pe4_receipt}"
			log "Receipt explorer copied with status $?"
		fi

		# Image Tool package
		if [ -e "${pit4_receipt_old}" -a ! -e "${pit4_receipt}" ]; then
			cp -f "${pit4_receipt_old}" "${pit4_receipt}"
			log "Receipt imagetool copied with status $?"
		fi

		# Transporter package
		if [ -e "${pt4_receipt_old}" -a ! -e "${pt4_receipt}" ]; then
			cp -f "${pt4_receipt_old}" "${pt4_receipt}"
			log "Receipt transporter copied with status $?"
		fi

		# Drivers 10.6 package
		if [ -e "${pdr106_receipt_old}" -a ! -e "${pdr106_receipt}" ]; then
			cp -f "${pdr106_receipt_old}" "${pdr106_receipt}"
			log "Receipt drivers 10.6 copied with status $?"
		fi
	fi
}

check_pd3_installation() {
	local pd3hyp='hypervisor.kext'
	[ -d "/System/Library/Extensions/${pd3hyp}" -o \
		-d "/Incompatible Software/${pd3hyp}" ] &&
			return 0
	return 1
}

check_stm_ver_installed() {
	if [ -d "/Library/Receipts/Parallels Switch To Mac ${1}.pkg" \
		   -o -f "/var/db/receipts/com.parallels.stm.plist" \
		   -o -d "/Library/Parallels/Receipts/Parallels Switch To Mac.pkg" \
		   -o -d "/Library/Parallels/Parallels Service.app/Contents/Receipts/Parallels Switch To Mac ${1}.pkg" ]; then
		check_service_ver "$1"
		return $?
	fi

	return 1
}

check_service_ver() {
	plist_file="/Library/Parallels/Parallels Service.app/Contents/Info.plist"
	grep -s -A 1 CFBundleShortVersionString "$plist_file" | grep -q "${1}\.0"
	return $?
}

get_parallels_version() {
	plist_file="/Library/Parallels/Parallels Service.app/Contents/Info.plist"
	parallels_ver=$(grep -s -A 1 CFBundleVersion "$plist_file" | tail -n 1 | sed 's/.*string>\(.*\)<\/string.*/\1/')
	echo "$parallels_ver"
}

check_pd_ver_installed() {
	if [ -d "/Library/Receipts/Parallels Desktop ${1}.pkg" \
		   -o -d "/Library/Parallels/Parallels Service.app/Contents/Receipts/Parallels Desktop ${1}.pkg" \
		   -o -d "/Library/Parallels/Receipts/Parallels Desktop.pkg" \
		   -o -f "/var/db/receipts/com.parallels.desktop.plist"\
		   -o -f "/var/db/receipts/com.parallels.pkg.desktop.plist" ]; then
		check_service_ver "$1"
		return $?
	fi

	return 1
}

locate_lsregister() {
	local LSREGISTER='/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister'
	if [ ! -x "${LSREGISTER}" ]; then

		#Find lsregister on system
		IFS=":"
		LSREGISTER_CANDIDATES=`locate lsregister | tr "\n" ":"`
		for lsregister_candidate in ${LSREGISTER_CANDIDATES}; do
			if [ -f "${lsregister_candidate}" -a -x "${lsregister_candidate}" ]; then
				LSREGISTER="${lsregister_candidate}"
				break
			fi
		done
		unset IFS

		# Check if lsregister exists
		if [ ! -x "${LSREGISTER}" ]; then
			log "No proper lsregister was found"
			LSREGISTER=""
		else
			log "lsregister path: ${LSREGISTER}"
		fi
	fi

	echo "${LSREGISTER}"
}

bundle_paths() {
	# We are interested in the next LS database entries:
	# bundle id:  <some_id>
	#         . . .
	#         path: <bundle_path>
	#         . . .
	#         identifier: <bundle_id> (<bundle_version>)
	#         . . .
	local bundle_id=$1
	awk -v in_bundle=0 "
		/^bundle[[:space:]]id:/ {
			in_bundle = 1
		}

		/^[[:space:]]*path:/ {
			if (in_bundle == 1) {
				bundle_path = \$0
				sub(\"[[:space:]]*path:[[:space:]]*\", \"\", bundle_path)
			}
		}

		/^[[:space:]]*identifier:[[:space:]]*${bundle_id}[[:space:]]/ {
			if (in_bundle == 1) {
				print bundle_path
				in_bundle = 0
			}
		}"
}

find_bundle_by_id() {
	bundle_id="${1}"
	candidates='/Applications/Parallels Desktop.app'

	if [ "x${PARALLELS_INTERNAL}" = "x" ]; then
		candidates=$(
			for user in /Users/*; do
				! test -d "${user}" && \
					continue

				# Get username
				username=${user##*/}

				id "${username}" > /dev/null 2>&1 || \
					continue

				sudo -u "${username}" "${LSREGISTER}" -dump |
					bundle_paths "${bundle_id}" |
					grep -E "^(/Applications/|/Users/${username}/)"
			done | sort -u)
	fi
	IFS=$'\n'
	for candidate in ${candidates}; do
		test -d "${candidate}" && \
			echo "${candidate}"
	done
	unset IFS
}

locate_bundle() {
	if [ -z "${1}" ]; then
		log "Warning: Empty argument in function locate_bundle"
		return 1
	fi
	if [ -z "${2}" ]; then
		log "Warning: No action specified"
		return 1
	fi

	log "Finding bundles for ${1}"
	local BUNDLE_ID="${1}"
	local ACTION="${2}"
	local LSREGISTER="$(locate_lsregister)"

	if [ -x "${LSREGISTER}" ]; then
		IFS=$'\n'
		# Get list of natural bundles with given ID
		BUNDLE_PATH=`find_bundle_by_id $BUNDLE_ID`
		for bundle in ${BUNDLE_PATH}; do
			#Check if it's a developer bundle
			if [ -f "${bundle}/Contents/MacOS/devel" ]; then
				log "Developer bundle ignored: ${bundle}"
			elif [ -z "`echo ${bundle} | sed 's,/$,,;s,^/[^/]*,,'`" -a "x${bundle}" != "x/Parallels Desktop.app" ]; then
				log "Ignore top-level path: ${bundle}"
			else
				log "Relocated bundle found: ${bundle}"
				# to use command with argument as ACTION we should restore IFS
				unset IFS
				${ACTION} "${bundle}"
				IFS=$'\n'
			fi
		done
		unset IFS
	fi
}

# Check for valid 'Parallels Desctop.app' bundles
check_other_pd_bundles() {
	local pd_id="com.parallels.desktop.console"
	local installation_paths=`locate_bundle "${pd_id}" "echo"`

	if [ -n "${installation_paths}" ]; then
		IFS=$'\n'
		for path in ${installation_paths}; do
			local plist="${path}/Contents/Info.plist"
			local major_ver=$(defaults read "${plist}" 'CFMajorVersion' 2>/dev/null)
			if [ -z "${major_ver}" ]; then
				log "Cannot get major version for bundle '${path}'"
				continue
			fi
			if [[ "x${BUNDLE_PATH}" != "x${path}" ]] && [[ "${major_ver}" -lt 8 ]]; then
				log "Other PD bundle detected (${path})."
				return 0
			fi
		done
		unset IFS
	fi

	return 1
}

# Check for Parallels Server
check_parallels_server() {
	if [ -d "/Library/Receipts/Install Parallels Server.pkg" ] || \
	   [ -d "/Library/Receipts/Parallels Server 3.pkg" ] || \
	   [ -d "/Library/Parallels/Parallels Service.app/Contents/Receipts/Parallels Server 3.pkg" ] || \
	   [ -d "/Library/Parallels/Receipts/Parallels Server 4.pkg" ] || \
	   [ -d "/Library/Parallels/Receipts/Parallels Server.pkg" ] || \
	   [ -f "/var/db/receipts/com.parallels.pkg.server.service.plist" ] || \
	   [ -f "/var/db/receipts/com.parallels.server.server.plist" ]
	then
		return 0
	fi

	return 1
}

# Check for another installed PD on the target machine
check() {
	# Check for PD installed (v3-7)
	dispatcher_info_plist="/Library/Parallels/Parallels Service.app/Contents/Info.plist"
	[ -f "${dispatcher_info_plist}" ] && return "${RC_OTHER_PD_DETECTED}"

	if check_parallels_server || \
	   check_pd_ver_installed 4 || check_pd_ver_installed 5 || \
	   check_stm_ver_installed 4 || check_stm_ver_installed 5 || \
	   check_pd3_installation
	then
		return "${RC_OTHER_PD_DETECTED}"
	fi

	check_other_pd_bundles && return "${RC_OTHER_PD_DETECTED}"

	return "${RC_OK}"
}

remove_pufs() {
	log "Removing PUFS from previous installation"
	if [ -e /var/db/receipts/com.parallels.prlufs.core.bom ] && \
	   [ -e /Library/Filesystems/prlufs.fs/Support/uninstall-macfuse-core.sh ]
	then
		/Library/Filesystems/prlufs.fs/Support/uninstall-macfuse-core.sh
		[ "$?" != "0" ] && log warning "Broken PUFS unstallation"
	fi

	if [ -e /Library/Filesystems/prlufs.fs ] && \
	   [ -e /System/Library/Filesystems/prlufs.fs ]
	then
		kextunload -b com.parallels.filesystems.prlufs > /dev/null 2>&1
		rm -rf /Library/Filesystems/prlufs.fs > /dev/null 2>&1
		rm -rf /System/Library/Filesystems/prlufs.fs > /dev/null 2>&1
	fi
}

# Uninstallation for versions 6 and 7
# TODO Check this.
remove_pd6() {
	local lib_prl='/Library/Parallels'
	local uninst="${lib_prl}/Uninstaller/Parallels Hypervisor/Uninstaller.sh"
	[ -r "${uninst}" ] || return

	local rcpt_dir='/var/db/receipts'
	local rcpt="${rcpt_dir}/com.parallels.pkg.virtualization.dispatcher.bom"
	if [ ! -e "${rcpt}" ]; then
		local plist="${lib_prl}/Parallels Service.app/Contents/Info.plist"
		# TODO Rework this ugly way of getting build number.
		local pd_build=$(cat "${plist}" | grep -A 1 CFBundleGetInfoString |
			grep string |
			sed 's;.*[[:digit:]]*\.\(\([[:digit:]]*\)\.[[:digit:]]*\).*;\2;'
			)
		# 15104 - the first flat binary PD release
		local uninst_tgz="${BUNDLE_PATH}/Contents/Resources/"
		if [ "${pd_build}" -lt 15104 ]; then
			uninst_tgz="${uninst_tgz}pkg-uninstaller.tgz"
		else
			uninst_tgz="${uninst_tgz}flatbinary-pkg-uninstaller.tgz"
		fi
		tar -xzf "${uninst_tgz}" -C /
	fi

	log "Running PDfM 6/7 uninstaller '${uninst}'"
	/bin/bash "${uninst}" --update
}

remove_pd3() {
	rm -rf '/Library/StartupItems/Parallels'
	rm -f '/Library/LaunchDaemons/pvsnatd.plist'

	local libprl_path='/Library/Parallels'
	rm -rf "${libprl_path}/Parallels Mounter.app"
	rm -rf "${libprl_path}/Tools"
	rm -rf "${libprl_path}/Bioses"
	rm -rf "${libprl_path}/Help"
	rm -rf "${libprl_path}/bugreports"
	rm -f "${libprl_path}/"*.pdf
	rm -f "${libprl_path}/.dhcp"*
	rmdir "${libprl_path}" 2>/dev/null

	local app_path='/Applications/Parallels'
	rm -rf "${app_path}/Parallels Image Tool.app"
	rm -rf "${app_path}/Parallels Desktop.app"
	rm -rf "${app_path}/Parallels Transporter.app"
	rm -rf "${app_path}/Parallels Explorer.app"
	rm -rf "${app_path}/Explorer.app"
	rm -rf "${app_path}/Parallels.app"
	rm -rf "${app_path}/ImageTool.app"
	rm -rf "${app_path}/Uninstall Parallels Desktop.app"
	rm -rf "${app_path}/Install"*
	rm -f "${app_path}/"*.pdf
	rm -f "${app_path}/.DS_Store"
	rmdir "${app_path}" 2>/dev/null

	local kexts_path='/System/Library/Extensions'
	rm -rf "${kexts_path}/ConnectUSB.kext"
	rm -rf "${kexts_path}/Pvsnet.kext"
	rm -rf "${kexts_path}/hypervisor.kext"
	rm -rf "${kexts_path}/vmmain.kext"
	touch "${kexts_path}"
}

# Cleanup OS X Incompatible Software stuff
cleanup_incomp_sw() {
	local incompat_path='/Incompatible Software'
	rm -rf "${incompat_path}/Parallels Service"*.app
	rm -rf "${incompat_path}/Parallels Mounter"*.app
	rm -rf "${incompat_path}/Parallels Explorer"*.app
	rm -rf "${incompat_path}/Parallels Transporter"*.app
	rm -rf "${incompat_path}/Parallels Image Tool"*.app
	rm -rf "${incompat_path}/Parallels Desktop"*.app
	rm -rf "${incompat_path}/ConnectUSB.kext"
	rm -rf "${incompat_path}/Pvsnet.kext"
	rm -rf "${incompat_path}/Pvsvnic.kext"
	rm -rf "${incompat_path}/hypervisor.kext"
	rm -rf "${incompat_path}/vmmain.kext"
	rm -f "${incompat_path}/com.parallels.desktop.launchdaemon.plist"
	rm -f "${incompat_path}/com.parallels.desktop.launch.plist"
	rm -f "${incompat_path}/com.parallels.DesktopControlAgent.plist"
	rm -f "${incompat_path}/com.parallels.vm.prl_pcproxy.plist"
	rm -f "${incompat_path}/pvsnatd.plist"
}

# Remove another installed PD (version less than 8)
remove() {
	if check_pd3_installation; then
		log "Running PDfM 3 uninstaller"
		remove_pd3
	fi

	snow_leopard_receipts_switcheroo

	if check_parallels_server; then
		log "Running PSfM uninstaller '${UNINSTALLER_SCRIPT}'"
		/bin/bash "${UNINSTALLER_SCRIPT}" server
	fi

	if check_pd_ver_installed 4 || check_pd_ver_installed 5; then
		log "Running PDfM 5/6 uninstaller '${UNINSTALLER_SCRIPT}'"
		/bin/bash "${UNINSTALLER_SCRIPT}" desktop
	fi

	if check_stm_ver_installed 4 || check_stm_ver_installed 5; then
		log "Running STM 5/6 uninstaller '${UNINSTALLER_SCRIPT}'"
		/bin/bash "${UNINSTALLER_SCRIPT}" stm
	fi

	remove_pd6

	# Remove all bundles with specified id (SBA and non-SBA)
	local pd_id="com.parallels.desktop.console"
	locate_bundle "${pd_id}" "rm -rf"
	###

	# Remove PUFS (MacFUSE)
	remove_pufs

	cleanup_incomp_sw
}


case $1 in
	check )
		check
		RC="$?"
	;;
	remove )
		remove
		RC="$?"
	;;
	* )
	echo "Usage: Uninstaller check|remove"
	;;
esac

exit "$RC"
